/**********
This library is free software; you can redistribute it and/or modify it under
the terms of the GNU Lesser General Public License as published by the
Free Software Foundation; either version 3 of the License, or (at your
option) any later version. (See <http://www.gnu.org/copyleft/lesser.html>.)

This library is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for
more details.

You should have received a copy of the GNU Lesser General Public License
along with this library; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
**********/
// Copyright (c) 1996-2018, Live Networks, Inc.  All rights reserved
//	Help by Carlo Bonamico to get working for Windows
// Delay queue
// Implementation

#include "include/DelayQueue.hh"
#include "../groupsock/include/GroupsockHelper.hh"

static const int MILLION = 1000000;

///// Timeval /////

int Timeval::operator>=(const Timeval &arg2) const {
    return seconds() > arg2.seconds()
           || (seconds() == arg2.seconds()
               && useconds() >= arg2.useconds());
}

void Timeval::operator+=(const DelayInterval &arg2) {
    secs() += arg2.seconds();
    usecs() += arg2.useconds();
    if (useconds() >= MILLION) {
        usecs() -= MILLION;
        ++secs();
    }
}

void Timeval::operator-=(const DelayInterval &arg2) {
    secs() -= arg2.seconds();
    usecs() -= arg2.useconds();
    if ((int) useconds() < 0) {
        usecs() += MILLION;
        --secs();
    }
    if ((int) seconds() < 0)
        secs() = usecs() = 0;

}

DelayInterval operator-(const Timeval &arg1, const Timeval &arg2) {
    time_base_seconds secs = arg1.seconds() - arg2.seconds();
    time_base_seconds usecs = arg1.useconds() - arg2.useconds();

    if ((int) usecs < 0) {
        usecs += MILLION;
        --secs;
    }
    if ((int) secs < 0)
        return DELAY_ZERO;
    else
        return DelayInterval(secs, usecs);
}


///// DelayInterval /////

DelayInterval operator*(short arg1, const DelayInterval &arg2) {
    time_base_seconds result_seconds = arg1 * arg2.seconds();
    time_base_seconds result_useconds = arg1 * arg2.useconds();

    time_base_seconds carry = result_useconds / MILLION;
    result_useconds -= carry * MILLION;
    result_seconds += carry;

    return DelayInterval(result_seconds, result_useconds);
}

#ifndef INT_MAX
#define INT_MAX	0x7FFFFFFF
#endif
const DelayInterval DELAY_ZERO(0, 0);
const DelayInterval DELAY_SECOND(1, 0);
const DelayInterval DELAY_MINUTE = 60 * DELAY_SECOND;
const DelayInterval DELAY_HOUR = 60 * DELAY_MINUTE;
const DelayInterval DELAY_DAY = 24 * DELAY_HOUR;
const DelayInterval ETERNITY(INT_MAX, MILLION - 1);
// used internally to make the implementation work


///// DelayQueueEntry /////

intptr_t DelayQueueEntry::tokenCounter = 0;

DelayQueueEntry::DelayQueueEntry(DelayInterval delay)
        : fDeltaTimeRemaining(delay) {
    fNext = fPrev = this;
    fToken = ++tokenCounter;
}

DelayQueueEntry::~DelayQueueEntry() {
}

void DelayQueueEntry::handleTimeout() {
    delete this;
}


///// DelayQueue /////

DelayQueue::DelayQueue()
        : DelayQueueEntry(ETERNITY) {
    fLastSyncTime = TimeNow();
}

DelayQueue::~DelayQueue() {
    while (fNext != this) {
        DelayQueueEntry *entryToRemove = fNext;
        removeEntry(entryToRemove);
        delete entryToRemove;
    }
}

void DelayQueue::addEntry(DelayQueueEntry *newEntry) {
    synchronize();

    DelayQueueEntry *cur = head();
    while (newEntry->fDeltaTimeRemaining >= cur->fDeltaTimeRemaining) {
        newEntry->fDeltaTimeRemaining -= cur->fDeltaTimeRemaining;
        cur = cur->fNext;
    }

    cur->fDeltaTimeRemaining -= newEntry->fDeltaTimeRemaining;

    // Add "newEntry" to the queue, just before "cur":
    newEntry->fNext = cur;
    newEntry->fPrev = cur->fPrev;
    cur->fPrev = newEntry->fPrev->fNext = newEntry;
}

void DelayQueue::updateEntry(DelayQueueEntry *entry, DelayInterval newDelay) {
    if (entry == NULL) return;

    removeEntry(entry);
    entry->fDeltaTimeRemaining = newDelay;
    addEntry(entry);
}

void DelayQueue::updateEntry(intptr_t tokenToFind, DelayInterval newDelay) {
    DelayQueueEntry *entry = findEntryByToken(tokenToFind);
    updateEntry(entry, newDelay);
}

void DelayQueue::removeEntry(DelayQueueEntry *entry) {
    if (entry == NULL || entry->fNext == NULL) return;

    entry->fNext->fDeltaTimeRemaining += entry->fDeltaTimeRemaining;
    entry->fPrev->fNext = entry->fNext;
    entry->fNext->fPrev = entry->fPrev;
    entry->fNext = entry->fPrev = NULL;
    // in case we should try to remove it again
}

DelayQueueEntry *DelayQueue::removeEntry(intptr_t tokenToFind) {
    DelayQueueEntry *entry = findEntryByToken(tokenToFind);
    removeEntry(entry);
    return entry;
}

DelayInterval const &DelayQueue::timeToNextAlarm() {
    if (head()->fDeltaTimeRemaining == DELAY_ZERO) return DELAY_ZERO; // a common case

    synchronize();
    return head()->fDeltaTimeRemaining;
}

void DelayQueue::handleAlarm() {
    if (head()->fDeltaTimeRemaining != DELAY_ZERO) synchronize();

    if (head()->fDeltaTimeRemaining == DELAY_ZERO) {
        // This event is due to be handled:
        DelayQueueEntry *toRemove = head();
        removeEntry(toRemove); // do this first, in case handler accesses queue

        toRemove->handleTimeout();
    }
}

DelayQueueEntry *DelayQueue::findEntryByToken(intptr_t tokenToFind) {
    DelayQueueEntry *cur = head();
    while (cur != this) {
        if (cur->token() == tokenToFind) return cur;
        cur = cur->fNext;
    }

    return NULL;
}

void DelayQueue::synchronize() {
    // First, figure out how much time has elapsed since the last sync:
    _EventTime timeNow = TimeNow();
    if (timeNow < fLastSyncTime) {
        // The system clock has apparently gone back in time; reset our sync time and return:
        fLastSyncTime = timeNow;
        return;
    }
    DelayInterval timeSinceLastSync = timeNow - fLastSyncTime;
    fLastSyncTime = timeNow;

    // Then, adjust the delay queue for any entries whose time is up:
    DelayQueueEntry *curEntry = head();
    while (timeSinceLastSync >= curEntry->fDeltaTimeRemaining) {
        timeSinceLastSync -= curEntry->fDeltaTimeRemaining;
        curEntry->fDeltaTimeRemaining = DELAY_ZERO;
        curEntry = curEntry->fNext;
    }
    curEntry->fDeltaTimeRemaining -= timeSinceLastSync;
}


///// _EventTime /////

_EventTime TimeNow() {
    struct timeval tvNow;

    gettimeofday(&tvNow, NULL);

    return _EventTime(tvNow.tv_sec, tvNow.tv_usec);
}

const _EventTime THE_END_OF_TIME(INT_MAX);
